from flask import Flask, request, make_response, send_from_directory
import traceback, importlib, time, sys, re, os, threading
from gevent.pywsgi import WSGIServer
import config


class HttpServer:
    def __init__(
            self,
            host='0.0.0.0',
            port=5000,  # 端口，默认5000
            route_table={},  # 路由表
            route_cache=True,  # 是否缓存路由
            err500_callback=None,  # 服务器内部错误时的回调，如果没有回调将会直接返回错误字符串
            headers={},  # 每个页面都会返回的headers
            before=None,  # 前置处理函数，如果前置处理函数有返回，将不再进行后续的处理逻辑，直接前置函数的数据返回请求端
            after=None,  # 后置处理函数，请注意后置处理函数的异常捕获，这里不再对后置处理的异常进行捕获
            use_wsgi=True,  # 是否使用wsgi
    ):
        """
            路由表为字典格式，举例如下：
                {
                    "request_path/get_user": {"m":"模块名称","f":"函数名称"},  # 绝对匹配路径
                    "req_path/<user_type>/<user_id>": {"m":"模块名称","f":"函数名称"},  # 路径变量的请求路径，路径变量通过 request.path_param 取得
                    "file/<filename>": {"fileDir":"静态资源所在路径"}， # 静态资源，路径变量名只能有 <filename> 一个，且只能在结尾
                    "index/index.html":{"fileDir":"静态资源所在路径","fileName":"静态资源文件名"},  # 静态资源
                }
        """
        # logging.getLogger('werkzeug').setLevel(logging.ERROR)  # 关掉原来的系统日志打印
        self.routeCache = {}  # 路由缓存 用于避免重复加载
        self.route_table = route_table  # 路由表
        self.allow_cache = route_cache  # 是否允许路径缓存 ， 注：缓存只是开发层面的，关闭缓存后修改的代码可实时体现出来
        self.cache = {}  # 缓存数据
        self.app = Flask(__name__, static_url_path='/乱起八遭的目录fadfdafadsfdasf')
        methods = ['POST', 'GET', 'OPTIONS']
        self.app.add_url_rule('/', view_func=self.main_entry, methods=methods)
        self.app.add_url_rule('/<path:path>', view_func=self.main_entry, methods=methods)
        self.err500_callback = err500_callback  # 服务器内部错误回调
        self.headers = headers
        if 'Server' not in self.headers:
            self.headers['Server'] = 'caoYongHttpServer,1.0'
        self.before = before  # 前置函数
        self.after = after  # 后置函数
        self.host = host
        self.port = port
        self.use_wsgi = use_wsgi

    def start_wsgi(self):
        WSGIServer((self.host, self.port), self.app, log=self.app.logger).serve_forever()

    def start(self):
        self.use_wsgi = False
        if self.use_wsgi:
            threading.Thread(target=self.start_wsgi, args=(), daemon=True).start()
        else:
            import logging
            log = logging.getLogger('werkzeug')
            log.setLevel(logging.ERROR)
            threading.Thread(target=self.app.run, args=(self.host, self.port, False), daemon=True).start()
        print('* Running on http://{}:{}/'.format(self.host, self.port))
        for ip in get_ips():
            print(' * http://{}:{}'.format(ip, self.port))

    def main_entry(self, path=''):
        """
            flask web 统一主入口
            print(request.form) #获取postDataForm 参数
            print(request.headers) #获取请求头部信息
            print(request.args) #获取get请求信息
            print(request.get_data().decode('utf-8')) #获取post原始信息
            print(request.cookies) #获取cookie
            :return:
        """

        print_log = True
        client_ip = request.remote_addr if request.headers.get('X-Real-IP') is None else request.headers.get('X-Real-IP')  # 获取客户端IP
        request.client_ip = client_ip
        request.cache = self.cache

        ret = None  # 返回结果
        if self.before:  # 前置处理
            try:
                ret = self.before(request)  # 前置处理，并取得返回
            except Exception as err:  # 前置处理出错
                error_str = str(err) + '\n' + str(traceback.format_exc())
                for sys_path in sys.path:
                    error_str = error_str.replace(sys_path, '')
                if self.err500_callback:
                    ret = self.err500_callback(request, error_str)
                else:
                    ret = error_str, 500, {}
        if ret is None:  # 如果前置处理有返回，就不再进行后续请求

            r_path = None  # 路由path
            if path in self.route_table:  # 判断请求路径是否在系统路由里
                r_path = path
            else:
                for route_path in self.route_table:  # 遍历所有支持模糊路径的路由
                    if '<' not in route_path or '>' not in route_path:
                        continue
                    is_hit, param = get_route(route_path, path)
                    if is_hit:  # 匹配到合适的路由
                        r_path = route_path  # 路由path
                        request.path_param = param  # 路径参数
                        break

            if r_path in self.route_table:
                if self.route_table[r_path].get('log') is False:
                    print_log = False
                try:
                    if 'm' in self.route_table[r_path]:  # api请求
                        if r_path in self.routeCache and self.allow_cache:
                            mm = self.routeCache[r_path]
                        else:
                            mk = __import__(self.route_table[r_path]['m'], fromlist=['xxx'])
                            mk = importlib.reload(mk)  # 重载模块
                            mm = eval('mk.'+self.route_table[r_path]['f'])
                            if self.allow_cache:  # 是否允许缓存
                                self.routeCache[r_path] = mm
                        ret = mm(request)

                    elif 'fileDir' in self.route_table[r_path]:  # 静态文件请求

                        if 'fileName' in self.route_table[r_path]:  # 如果路由指定了文件名
                            filename = self.route_table[r_path]['fileName']
                        else:
                            filename = param['filename']  # 路由没有指定文件名

                        ret = static_file(self.route_table[r_path]['fileDir'], filename)
                    else:
                        ret = '404', 404, {}
                except Exception as err:
                    error_str = str(err) + '\n' + str(traceback.format_exc())
                    for sys_path in sys.path:
                        error_str = error_str.replace(sys_path, '')
                    if self.err500_callback:
                        ret = self.err500_callback(request, error_str)
                    else:
                        ret = error_str, 500, {}
            else:
                ret = '404', 404, {}

        if type(ret) != tuple:  # 如果返回的不是元组，则强制设为元组
            ret = ret, 200, {}
        for _ in self.headers:  # 处理公共headers
            if _ not in ret[2]:  # 判断这个公共header是否已被覆盖
                ret[2][_] = self.headers[_]  # 赋值公共header

        if self.after:
            try:
                self.after(request, ret)  # 后置处理
            except:pass

        print_msg = '{} - - [{}] "{} {}" {}'.format(client_ip, time.strftime('%Y-%m-%d %H:%M:%S'), request.method, request.path, ret[1])
        if request.method not in ['OPTIONS'] and print_log:
            print(print_msg)
        return ret


def get_ips():
    ips = []
    if 'linux' in sys.platform:
        ppr = os.popen('ifconfig')
        te1 = ppr.read()
        ppr.close()

        for temm in te1.split('\n'):
            if 'inet' in temm and 'netmask' in temm:
                ips.append(temm.lstrip().split(' ')[1])
    else:
        ppr = os.popen('ipconfig')
        te1 = ppr.read()
        ppr.close()

        for temm in te1.split('\n'):
            if 'ipv4' in temm.lower() and ':' in temm:
                ips.append(temm.split(':')[-1].strip())
    return ips


def static_file(file_dir, filename):

    if ':' in filename and filename[1] == ':':
        filename = filename[3:]
    if filename[0] in ['/', '\\']:
        filename = filename[1:]
    # print(file_dir, filename)
    file_path = os.path.join(file_dir, filename).replace('\\', '/')
    if os.path.isfile(file_path):
        local_filename = file_path.split('/')[-1]
        ret = make_response(send_from_directory(file_path.replace(local_filename, ''), local_filename))
        ret.cache_control.max_age = 3600*24*31
        ret.cache_control.no_cache = False
    else:
        ret = '404', 404, {}
    return ret


def get_route(sys_route, req_path):
    path_part = [{'_': _, 's': -1, 'e': -1} for _ in re.split('(<.*?>)', sys_route) if _]  # 按关键字路径拆分
    beg = 0  # 搜索的起始位置
    param = {}
    is_hit = False
    for i, pap in enumerate(path_part):
        _ = pap['_']
        if _.startswith('<') and _.endswith('>'):
            if i == 0:
                pap['s'] = 0
            elif i == len(path_part) - 1:
                pap['e'] = req_path.__len__()
        else:
            n_beg = req_path.find(_, beg)

            if i == 0 and n_beg > 0:
                break
            elif n_beg < 0:
                break
            elif i == len(path_part) - 1 and req_path[n_beg:] != _:
                break
            else:
                beg = n_beg + _.__len__()
                pap['s'] = n_beg
                pap['e'] = n_beg + _.__len__()
                prev_index = i - 1
                next_index = i + 1
                if prev_index >= 0:
                    path_part[prev_index]['e'] = pap['s']
                if next_index < len(path_part):
                    path_part[next_index]['s'] = pap['e']
    else:
        is_hit = True
        for pap in path_part:
            if pap['_'].startswith('<') and pap['_'].endswith('>'):
                param[pap['_'][1:-1]] = req_path[pap['s']:pap['e']]

    return is_hit, param


if __name__ == '__main__':
    pass

